package org.web.hello.dubbomarket.server.controller;

import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.multipart.MultipartFile;
import org.web.base.domain.ViewResultHelper;
import org.web.base.domain.exception.ResultMessageEnum;
import org.web.base.domain.exception.ServiceException;
import org.web.base.domain.helper.ServiceExceptionHelper;
import org.web.base.domain.view.ViewResultDO;
import org.web.base.domain.ResultDO;
import org.web.base.helper.*;
import org.web.base.helper.excel.ExcelOperator;
import org.web.base.helper.excel.ExcelHelper;
import org.web.base.helper.excel.OneRowDTO;
import org.web.base.helper.excel.OneTask;

import java.io.InputStream;
import java.lang.reflect.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/***
 * 只用一个类下载所有实体的Excel文件。 完全继承查询接口提供的功能。
 * 未来的扩展功能：
 * 1. 增加权限控制，数据权限，围绕entityName的权限控制.
 * 2. 控制下载Excel的数量，避免下载过大的文件。
 * 3. 返回记录总数。
 * */
@Controller()
@RequestMapping("dubboMarketServerExcel")
public class ExcelController {
    private static final Logger logger = LogManager.getLogger(ExcelController.class);
    private static final String QUERY_BASE_PACKAGE = "org.web.hello.dubbomarket.server.domain.query.";
    private static final String DTO_BASE_PACKAGE = "org.web.hello.dubbomarket.sdk.dto.";
    @Autowired
    private BeanFactory beanFactory;

    /**
     * 未来的扩展功能：
     * 1. 增加权限控制，数据权限，围绕entityName的权限控制.
     * 2. 控制下载Excel的数量，避免下载过大的文件。
     * 3. 返回记录总数。
     */
    @RequestMapping("/downloadExcel")
    @ResponseBody
    public ResponseEntity<Object> downloadExcel(ExcelOperator excelOperator, @RequestParam Map<String, Object> params) {

        try {
            initExcelOperator(excelOperator);
            checkExcelOperator(beanFactory, excelOperator);
            Class queryClass = Class.forName(excelOperator.getQueryClassName());
            Class dtoClass = Class.forName(excelOperator.getDtoClassName());
            Object queryBean = BeanConvertHelper.convertMapToBean(params, queryClass);

            ResultDO resultDO = getResultDOByQuery(excelOperator, queryBean, beanFactory);
            if (resultDO.isFailed()) {
                throw ServiceExceptionHelper.buildServiceException(resultDO);
            }
            List<OneTask> queryTaskList = new ArrayList<>();
            OneTask oneTask = new OneTask();
            String entityName = dtoClass.getSimpleName().replace("DTO", "");
            oneTask.setTaskMark("Query-" + entityName + "-1");
            oneTask.setQueryObject(queryBean);
            oneTask.setResultDO(resultDO);
            queryTaskList.add(oneTask);
            String fileName = buildDownloadExcelFileName(excelOperator);
            byte[] excelContent = ExcelHelper.exportDataToExcel(queryTaskList, excelOperator, false);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", fileName);
            return new ResponseEntity<>(excelContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return getResponseEntityByException(e);
        }
    }


    @RequestMapping("/getGeneralList")
    @ResponseBody
    public ViewResultDO<List<Object>> getGeneralList(ExcelOperator excelOperator, @RequestParam Map<String, Object> params) {

        try {
            initExcelOperator(excelOperator);
            checkExcelOperator(beanFactory, excelOperator);
            Class queryClass = Class.forName(excelOperator.getQueryClassName());
            Object queryBean = BeanConvertHelper.convertMapToBean(params, queryClass);
            ResultDO resultDO = getResultDOByQuery(excelOperator, queryBean, beanFactory);
            if (resultDO.isFailed()) {
                throw ServiceExceptionHelper.buildServiceException(resultDO);
            }
            Class dtoClass = Class.forName(excelOperator.getDtoClassName());
            ViewResultDO<List<Object>> viewResultDO = ViewResultHelper.convertToViewResultByResultDO(resultDO, dtoClass);
            return viewResultDO;
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return ViewResultHelper.buildViewResultByServiceException(e, "查询失败");
        }
    }

    /***
     * 根据上传的文件下载Excel文件，上传的文件中包含查询条件。
     * 下载文件的sheet名称为 , 由查询条件的rowMark组成。
     * */
    @PostMapping("/downloadExcelByFile")
    public ResponseEntity<Object> downloadExcelByFile(@RequestParam("file") MultipartFile file, ExcelOperator excelOperator) {

        try {
            initExcelOperator(excelOperator);
            checkExcelOperator(beanFactory, excelOperator);

            Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());

            if (file.isEmpty()) {
                throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "上传文件为空。");
            }
            InputStream inputStream = file.getInputStream();
            // 使用 Apache POI 读取 Excel 文件
            Workbook workbook = new XSSFWorkbook(inputStream);
			inputStream.close(); // 读完立即关闭
            Sheet sheet = workbook.getSheet("DownloadQuery");
            // 遍历 Excel 表格
            Class queryClazz = Class.forName(excelOperator.getQueryClassName());
            List<OneRowDTO> queryList = ExcelHelper.sheetToListMap(sheet, queryClazz);

            Object serviceBean = beanFactory.getBean(excelOperator.getServiceBeanName());
            Method method = serviceBean.getClass().getMethod(excelOperator.getSelectMethodName(), queryClass);
            method.setAccessible(true);

            List<OneTask> queryTaskList = new ArrayList<>();
            Class dtoClass = Class.forName(excelOperator.getDtoClassName());
            int i = 1;
            for (OneRowDTO oneRowDTO : queryList) {
                Object queryBean = BeanConvertHelper.convertMapToBean(oneRowDTO.getRowDataMap(), queryClass);
                ResultDO resultDO = (ResultDO<List<?>>) method.invoke(serviceBean, queryBean);
                OneTask oneTask = new OneTask();
                String entityName = dtoClass.getSimpleName().replace("DTO", "");
                if (StringUtils.isNotBlank(oneRowDTO.getRowMark())) {
                    oneTask.setTaskMark(oneRowDTO.getRowMark());
                } else {
                    oneTask.setTaskMark("Query-" + entityName + "-" + i);
                }
                oneTask.setQueryObject(queryBean);
                oneTask.setResultDO(resultDO);
                queryTaskList.add(oneTask);
                i++;
            }

            byte[] excelContent = ExcelHelper.exportDataToExcel(queryTaskList, excelOperator, false);

            String fileName = buildDownloadExcelFileName(excelOperator);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", fileName);
            return new ResponseEntity<>(excelContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return getResponseEntityByException(e);
        }
    }

    private static ResultDO<List<?>> getResultDOByQuery(ExcelOperator excelOperator, Object queryBean, BeanFactory beanFactory) throws Exception {
        Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());
        Object serviceBean = beanFactory.getBean(excelOperator.getServiceBeanName());
        Method method = serviceBean.getClass().getMethod(excelOperator.getSelectMethodName(), queryClass);
        method.setAccessible(true);
        ResultDO<List<?>> resultDO = (ResultDO<List<?>>) method.invoke(serviceBean, queryBean);
        return resultDO;
    }

    /***
     * 默认从service层获取数据，当然也可以指定从BIZ层获取数据。
     * 二开时，需要考虑存在案例漏洞的风险，从两个方面入手：限死 【selectMethodName】名称，限定【entityName】范围。
     * 初始化的规则，可以调整
     * @param excelOperator 下载标识对象
     * */
    private static void initExcelOperator(ExcelOperator excelOperator) {

        if (excelOperator == null) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "下载标识对象为空。");
        } else if (excelOperator.getEntityName() == null || excelOperator.getEntityName().isEmpty()) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "entityName信息不能为空。");
        }
        String entityName = excelOperator.getEntityName();
        entityName = StringHelper.initUpper(entityName);
        String initLowerEntityName = StringHelper.initLower(entityName);
        if (excelOperator.getServiceBeanName() == null || excelOperator.getServiceBeanName().isEmpty()) {
            excelOperator.setServiceBeanName(initLowerEntityName + "Service");
        }

        if (excelOperator.getQueryClassName() == null || excelOperator.getQueryClassName().isEmpty()) {
            excelOperator.setQueryClassName(QUERY_BASE_PACKAGE + "Query" + entityName);
        }

        if (excelOperator.getDomainClassName() == null || excelOperator.getDomainClassName().isEmpty()) {
            excelOperator.setDomainClassName(QUERY_BASE_PACKAGE + entityName + "DO");
        }

        if (excelOperator.getDtoClassName() == null || excelOperator.getDtoClassName().isEmpty()) {
            excelOperator.setDtoClassName(DTO_BASE_PACKAGE + entityName + "DTO");
        }

        if (excelOperator.getSelectMethodName() == null || excelOperator.getSelectMethodName().isEmpty()) {
            excelOperator.setSelectMethodName("select" + entityName + "ListResult");
        }
    }

    /**
     * 校验资源是否存在，主要是检查serviceBeanName、dtoClassName、queryClassName是否存在。
     *
     * @param beanFactory   spring bean工厂
     * @param excelOperator 下载标识对象
     * @throws ServiceException 资源不存在异常
     */
    private static void checkExcelOperator(BeanFactory beanFactory, ExcelOperator excelOperator) {
        if (!beanFactory.containsBean(excelOperator.getServiceBeanName())) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "查询资源不存。访问资源entityName信息： " + excelOperator.getEntityName());
        } else if (!beanFactory.containsBean(excelOperator.getServiceBeanName())) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "查询参数类不存在。访问资源serviceBeanName信息： " + excelOperator.getServiceBeanName());
        }
        try {
            Class.forName(excelOperator.getDtoClassName());
            Class.forName(excelOperator.getQueryClassName());
        } catch (ClassNotFoundException e) {
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "DTO或查询参数类不存在。访问资源dtoClassName或queryClassName信息： " + excelOperator.getDtoClassName() + " 或 " + excelOperator.getQueryClassName());
        }
    }

    /**
     * Exception处理，返回 ResponseEntity 对象.
     * 主要针对执行checkExcelOperator方法时出现的异常，返回 ResponseEntity 对象。
     * 也包括其他系统异常，如数据库操作异常等。
     *
     * @param e 异常
     * @return ResponseEntity
     */
    private static ResponseEntity getResponseEntityByException(Exception e) {
        String errorMsg = "下载Excel失败。";
        if (e instanceof ServiceException) {
            errorMsg = errorMsg + ((ServiceException) e).getDescription();
        } else {
            errorMsg = errorMsg + e;
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(new MediaType("text", "plain", StandardCharsets.UTF_8));
        ViewResultDO viewResultDO = new ViewResultDO();
        viewResultDO.setResult(false);
        viewResultDO.setMsg(errorMsg);
        errorMsg = new Gson().toJson(viewResultDO);
        return new ResponseEntity<>(errorMsg, headers, HttpStatus.OK);
    }


    private static String buildDownloadExcelFileName(ExcelOperator excelOperator) {
        String currentTime = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date());
        return "Data-" + excelOperator.getEntityName() + "_" + currentTime + ".xlsx";
    }
}
